import { EventEmitter } from "events";
import {
  OutputBufferEntry,
  BufferReadOptions,
  BufferReadResult,
} from "./types.js";

/**
 * 终端输出缓冲器
 * 负责缓存终端输出，支持历史查询和实时流式读取
 */
export class OutputBuffer extends EventEmitter {
  private buffer: OutputBufferEntry[] = [];
  private maxSize: number;
  private currentLineNumber = 0;
  private terminalId: string;
  private currentLineEntry: OutputBufferEntry | null = null;
  private sequenceCounter = 0;
  private oldestSequence = 0;
  private latestSequence = 0;

  // Spinner detection and throttling
  private compactAnimations: boolean;
  private animationThrottleMs: number;
  private spinnerBuffer: string = "";
  private spinnerCount: number = 0;
  private lastSpinnerFlush: number = 0;
  private spinnerFlushTimer: NodeJS.Timeout | null = null;

  // Common spinner characters used by npm, yarn, pnpm, etc.
  private static readonly SPINNER_CHARS = new Set([
    "⠋",
    "⠙",
    "⠹",
    "⠸",
    "⠼",
    "⠴",
    "⠦",
    "⠧",
    "⠇",
    "⠏", // Braille spinners
    "◐",
    "◓",
    "◑",
    "◒", // Circle spinners
    "◴",
    "◷",
    "◶",
    "◵", // Quarter circle spinners
    "◰",
    "◳",
    "◲",
    "◱", // Box spinners
    "▖",
    "▘",
    "▝",
    "▗", // Block spinners
    "|",
    "/",
    "-",
    "\\", // Classic ASCII spinner
    "●",
    "○",
    "◉",
    "◎", // Dot spinners
  ]);

  constructor(
    terminalId: string,
    maxSize = 10000,
    options: { compactAnimations?: boolean; animationThrottleMs?: number } = {},
  ) {
    super();
    this.terminalId = terminalId;
    this.maxSize = maxSize;
    this.compactAnimations = options.compactAnimations ?? true;
    this.animationThrottleMs = options.animationThrottleMs ?? 100;
    this.sequenceCounter = 0;
  }

  private nextSequence(): number {
    const next = ++this.sequenceCounter;
    this.latestSequence = next;
    if (this.oldestSequence === 0) {
      this.oldestSequence = next;
    }
    return next;
  }

  private stampSequence(entry: OutputBufferEntry): void {
    entry.sequence = this.nextSequence();
  }

  /**
   * 检测字符串是否主要包含 spinner 字符
   */
  private isSpinnerLine(content: string): boolean {
    if (!content || content.length === 0) return false;

    // Remove ANSI escape sequences for analysis
    const cleanContent = content.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");

    // Check if the line contains spinner characters
    let spinnerCharCount = 0;
    for (const char of cleanContent) {
      if (OutputBuffer.SPINNER_CHARS.has(char)) {
        spinnerCharCount++;
      }
    }

    // If more than 30% of visible characters are spinner chars, consider it a spinner line
    const visibleChars = cleanContent.replace(/\s/g, "").length;
    return visibleChars > 0 && spinnerCharCount / visibleChars > 0.3;
  }

  /**
   * 刷新 spinner 缓冲区
   */
  private flushSpinnerBuffer(
    newEntries: OutputBufferEntry[],
    force: boolean = false,
    markUpdated?: (entry: OutputBufferEntry | null) => void,
  ): void {
    if (!this.compactAnimations) return;

    const now = Date.now();
    const timeSinceLastFlush = now - this.lastSpinnerFlush;

    // Only flush if forced or enough time has passed
    if (!force && timeSinceLastFlush < this.animationThrottleMs) {
      return;
    }

    if (this.spinnerCount > 0) {
      // Create a compact representation of the spinner updates
      const compactMessage = this.spinnerBuffer
        ? this.spinnerBuffer
        : `[spinner ×${this.spinnerCount}]`;

      const line = this.touchCurrentLine(newEntries, true);
      if (line) {
        line.content = compactMessage;
        if (markUpdated) {
          markUpdated(line);
        } else {
          this.stampSequence(line);
        }
      }

      this.spinnerBuffer = "";
      this.spinnerCount = 0;
      this.lastSpinnerFlush = now;
    }
  }

  /**
   * 清除 spinner 刷新定时器
   */
  private clearSpinnerTimer(): void {
    if (this.spinnerFlushTimer) {
      clearTimeout(this.spinnerFlushTimer);
      this.spinnerFlushTimer = null;
    }
  }

  /**
   * 创建新的缓冲条目
   */
  private createEntry(
    initialContent: string,
    newEntries: OutputBufferEntry[],
    skipIfDuplicateBlank: boolean,
  ): OutputBufferEntry | null {
    if (
      skipIfDuplicateBlank &&
      initialContent === "" &&
      this.buffer.length > 0 &&
      this.buffer[this.buffer.length - 1]!.content === ""
    ) {
      return null;
    }

    const entry: OutputBufferEntry = {
      timestamp: new Date(),
      content: initialContent,
      lineNumber: this.currentLineNumber++,
      sequence: this.nextSequence(),
    };

    this.buffer.push(entry);
    newEntries.push(entry);
    this.trimBuffer();

    return entry;
  }

  private touchCurrentLine(
    newEntries: OutputBufferEntry[],
    reuseLast = false,
  ): OutputBufferEntry | null {
    if (this.currentLineEntry) {
      if (!newEntries.includes(this.currentLineEntry)) {
        newEntries.push(this.currentLineEntry);
      }
      return this.currentLineEntry;
    }

    if (reuseLast && this.buffer.length > 0) {
      const entry = this.buffer[this.buffer.length - 1]!;
      this.currentLineEntry = entry;
      if (!newEntries.includes(entry)) {
        newEntries.push(entry);
      }
      return entry;
    }

    const entry = this.createEntry("", newEntries, false);
    this.currentLineEntry = entry;
    return entry;
  }

  /**
   * 结束当前行，将其标记为完成
   */
  private finalizeCurrentLine(newEntries: OutputBufferEntry[]): void {
    if (!this.currentLineEntry) {
      const entry = this.createEntry("", newEntries, true);
      if (entry) {
        this.stampSequence(entry);
      }
    } else if (this.currentLineEntry.content === "") {
      const lastIndex = this.buffer.length - 1;
      if (lastIndex >= 0 && this.buffer[lastIndex] === this.currentLineEntry) {
        const previous = this.buffer[lastIndex - 1];
        if (previous && previous.content === "") {
          this.buffer.pop();
        }
      }
    }

    this.currentLineEntry = null;
  }

  /**
   * 修剪缓冲区，确保不超过最大容量
   */
  private trimBuffer(): void {
    while (this.buffer.length > this.maxSize) {
      const removed = this.buffer.shift();
      if (removed && this.currentLineEntry === removed) {
        this.currentLineEntry = null;
      }
      if (removed) {
        this.oldestSequence =
          this.buffer.length > 0
            ? this.buffer[0]!.sequence
            : this.latestSequence;
      }
    }
  }

  private consumeEscapeSequence(
    input: string,
    startIndex: number,
  ): { sequence: string; nextIndex: number } {
    let endIndex = startIndex + 1;

    if (endIndex >= input.length) {
      return {
        sequence: input[startIndex]!,
        nextIndex: startIndex,
      };
    }

    const nextChar = input[endIndex]!;

    if (nextChar === "[") {
      endIndex++;
      while (endIndex < input.length) {
        const ch = input[endIndex]!;
        if (
          (ch >= "0" && ch <= "9") ||
          ch === ";" ||
          ch === "?" ||
          ch === ":" ||
          ch === ">" ||
          ch === "<"
        ) {
          endIndex++;
          continue;
        }
        endIndex++;
        break;
      }
    } else if (nextChar === "]") {
      endIndex++;
      while (endIndex < input.length) {
        const ch = input[endIndex]!;
        if (ch === "") {
          endIndex++;
          break;
        }
        if (ch === "" && input[endIndex + 1] === "\\") {
          endIndex += 2;
          break;
        }
        endIndex++;
      }
    } else {
      endIndex++;
    }

    if (endIndex > input.length) {
      endIndex = input.length;
    }

    return {
      sequence: input.slice(startIndex, endIndex),
      nextIndex: endIndex - 1,
    };
  }

  private handleEscapeSequence(
    sequence: string,
    newEntries: OutputBufferEntry[],
    markUpdated: (entry: OutputBufferEntry | null) => void,
  ): void {
    if (!sequence || sequence.length === 0) {
      return;
    }

    if (sequence.startsWith("[")) {
      const finalChar = sequence[sequence.length - 1]!;

      switch (finalChar) {
        case "K":
        case "J":
        case "G":
        case "D":
        case "C": {
          // When we receive erase/move sequences after a newline, ensure we
          // operate on the current (possibly new) line instead of mutating the
          // previously finalized entry.
          const line = this.touchCurrentLine(newEntries);
          if (line) {
            line.content = "";
            markUpdated(line);
          }
          break;
        }
        default:
          break;
      }
    }
    // 其他 ANSI 转义序列（如颜色设置）对文本内容无影响，这里直接忽略
  }

  /**
   * 添加新的输出内容
   */
  append(content: string): void {
    if (!content) return;

    const newEntries: OutputBufferEntry[] = [];
    const updatedEntries = new Set<OutputBufferEntry>();

    const markUpdated = (entry: OutputBufferEntry | null) => {
      if (!entry) {
        return;
      }
      entry.timestamp = new Date();
      if (!newEntries.includes(entry)) {
        newEntries.push(entry);
      }
      if (!updatedEntries.has(entry)) {
        this.stampSequence(entry);
        updatedEntries.add(entry);
      }
    };

    for (let i = 0; i < content.length; i++) {
      const char = content[i]!;

      if (char === "") {
        const { sequence, nextIndex } = this.consumeEscapeSequence(content, i);
        this.handleEscapeSequence(sequence, newEntries, markUpdated);
        i = nextIndex;
        continue;
      }

      if (char === "\r") {
        const nextChar = content[i + 1];
        if (nextChar === "\n") {
          // Flush any pending spinner before finalizing line
          if (this.compactAnimations && this.currentLineEntry) {
            const isSpinner = this.isSpinnerLine(this.currentLineEntry.content);
            if (isSpinner) {
              this.spinnerCount++;
              this.spinnerBuffer = this.currentLineEntry.content;

              // Schedule a flush if not already scheduled
              this.clearSpinnerTimer();
              this.spinnerFlushTimer = setTimeout(() => {
                const flushEntries: OutputBufferEntry[] = [];
                this.flushSpinnerBuffer(flushEntries, true, markUpdated);
                if (flushEntries.length > 0) {
                  this.emit("data", flushEntries);
                }
              }, this.animationThrottleMs);

              // Clear current line to prevent it from being finalized
              this.currentLineEntry = null;
            } else {
              // Non-spinner content, flush any pending spinners
              this.flushSpinnerBuffer(newEntries, true, markUpdated);
            }
          }

          this.finalizeCurrentLine(newEntries);
          i++; // Skip the '\n' as we've already handled the newline
        } else {
          // Carriage return without newline: check if it's a spinner update
          if (this.compactAnimations && this.currentLineEntry) {
            const isSpinner = this.isSpinnerLine(this.currentLineEntry.content);
            if (isSpinner) {
              this.spinnerCount++;
              this.spinnerBuffer = this.currentLineEntry.content;

              // Schedule a flush
              this.clearSpinnerTimer();
              this.spinnerFlushTimer = setTimeout(() => {
                const flushEntries: OutputBufferEntry[] = [];
                this.flushSpinnerBuffer(flushEntries, true, markUpdated);
                if (flushEntries.length > 0) {
                  this.emit("data", flushEntries);
                }
              }, this.animationThrottleMs);
            } else {
              // Non-spinner content, flush any pending spinners
              this.flushSpinnerBuffer(newEntries, true, markUpdated);
            }
          }

          // Overwrite current line
          const line = this.touchCurrentLine(newEntries, true);
          if (line) {
            line.content = "";
            markUpdated(line);
          }
        }
        continue;
      }

      if (char === "\n") {
        // Flush any pending spinner before finalizing line
        if (this.compactAnimations && this.currentLineEntry) {
          const isSpinner = this.isSpinnerLine(this.currentLineEntry.content);
          if (!isSpinner) {
            this.flushSpinnerBuffer(newEntries, true, markUpdated);
          }
        }

        this.finalizeCurrentLine(newEntries);
        continue;
      }

      const line = this.touchCurrentLine(newEntries);
      if (line) {
        line.content += char;
        markUpdated(line);
      }
    }

    if (newEntries.length > 0) {
      this.emit("data", newEntries);
    }
  }

  /**
   * 读取缓冲区内容
   */
  read(options: BufferReadOptions = {}): BufferReadResult {
    const { since = 0, maxLines = 1000 } = options;

    const filtered = this.buffer.filter((entry) => entry.sequence > since);
    const entries = maxLines ? filtered.slice(-maxLines) : filtered;
    const truncated = Boolean(maxLines && filtered.length > entries.length);
    const nextCursor =
      entries.length > 0 ? entries[entries.length - 1]!.sequence : since;
    const hasMore =
      truncated ||
      (this.oldestSequence > 0 && since > 0 && since < this.oldestSequence);

    return {
      entries,
      totalLines: this.currentLineNumber,
      hasMore,
      nextCursor,
    };
  }

  /**
   * 智能读取：支持头尾模式
   */
  readSmart(
    options: {
      since?: number;
      mode?: "full" | "head-tail" | "head" | "tail";
      headLines?: number;
      tailLines?: number;
      maxLines?: number;
    } = {},
  ): {
    entries: OutputBufferEntry[];
    totalLines: number;
    hasMore: boolean;
    truncated: boolean;
    nextCursor: number;
    stats: {
      totalBytes: number;
      estimatedTokens: number;
      linesShown: number;
      linesOmitted: number;
    };
  } {
    const {
      since = 0,
      mode = "full",
      headLines = 50,
      tailLines = 50,
      maxLines = 1000,
    } = options;

    const allEntries = this.buffer.filter((entry) => entry.sequence > since);
    let resultEntries: OutputBufferEntry[] = [];
    let truncated = false;
    let linesOmitted = 0;

    // 计算总字节数和估算 token 数
    const totalText = allEntries.map((e) => e.content).join("\n");
    const totalBytes = Buffer.byteLength(totalText, "utf8");
    const estimatedTokens = Math.ceil(totalText.length / 4); // 粗略估算：4字符≈1token

    switch (mode) {
      case "head":
        if (allEntries.length > headLines) {
          resultEntries = allEntries.slice(0, headLines);
          truncated = true;
          linesOmitted = allEntries.length - headLines;
        } else {
          resultEntries = allEntries;
        }
        break;

      case "tail":
        if (allEntries.length > tailLines) {
          resultEntries = allEntries.slice(-tailLines);
          truncated = true;
          linesOmitted = allEntries.length - tailLines;
        } else {
          resultEntries = allEntries;
        }
        break;

      case "head-tail":
        if (allEntries.length > headLines + tailLines) {
          const head = allEntries.slice(0, headLines);
          const tail = allEntries.slice(-tailLines);
          resultEntries = [...head, ...tail];
          truncated = true;
          linesOmitted = allEntries.length - headLines - tailLines;
        } else {
          resultEntries = allEntries;
        }
        break;

      case "full":
      default:
        if (maxLines && allEntries.length > maxLines) {
          resultEntries = allEntries.slice(-maxLines);
          truncated = true;
          linesOmitted = allEntries.length - maxLines;
        } else {
          resultEntries = allEntries;
        }
        break;
    }

    const hasMore =
      this.oldestSequence > 0 && since > 0 && since < this.oldestSequence;

    const nextCursor =
      resultEntries.length > 0
        ? resultEntries[resultEntries.length - 1]!.sequence
        : since;

    return {
      entries: resultEntries,
      totalLines: this.currentLineNumber,
      hasMore,
      truncated,
      nextCursor,
      stats: {
        totalBytes,
        estimatedTokens,
        linesShown: resultEntries.length,
        linesOmitted,
      },
    };
  }

  /**
   * 获取最新的输出内容
   */
  getLatest(maxLines = 100): OutputBufferEntry[] {
    const startIndex = Math.max(0, this.buffer.length - maxLines);
    return this.buffer.slice(startIndex);
  }

  /**
   * 获取所有输出内容的文本形式
   */
  getAllText(): string {
    return this.buffer.map((entry) => entry.content).join("\n");
  }

  /**
   * 获取指定范围的文本内容
   */
  getText(since = 0, maxLines = 1000): string {
    const result = this.read({ since, maxLines });
    return result.entries.map((entry) => entry.content).join("\n");
  }

  /**
   * 清空缓冲区
   */
  clear(): void {
    this.clearSpinnerTimer();
    this.buffer = [];
    this.currentLineNumber = 0;
    this.currentLineEntry = null;
    this.spinnerBuffer = "";
    this.spinnerCount = 0;
    this.lastSpinnerFlush = 0;
    this.sequenceCounter = 0;
    this.oldestSequence = 0;
    this.latestSequence = 0;
    this.emit("clear");
  }

  /**
   * 获取缓冲区统计信息
   */
  getStats() {
    return {
      terminalId: this.terminalId,
      totalLines: this.currentLineNumber,
      bufferedLines: this.buffer.length,
      maxSize: this.maxSize,
      oldestLine: this.buffer.length > 0 ? this.buffer[0]!.lineNumber : 0,
      newestLine:
        this.buffer.length > 0
          ? this.buffer[this.buffer.length - 1]!.lineNumber
          : 0,
    };
  }

  /**
   * 设置最大缓冲区大小
   */
  setMaxSize(maxSize: number): void {
    this.maxSize = maxSize;

    // 如果当前缓冲区超过新的最大大小，删除最旧的条目
    this.trimBuffer();
  }

  /**
   * 获取当前行号
   */
  getCurrentLineNumber(): number {
    return this.currentLineNumber;
  }

  /**
   * 检查是否有指定行号之后的新内容
   */
  hasNewContent(since: number): boolean {
    return this.currentLineNumber > since;
  }

  /**
   * 设置动画压缩选项
   */
  setCompactAnimations(enabled: boolean): void {
    this.compactAnimations = enabled;
    if (!enabled) {
      // If disabling, flush any pending spinners
      this.clearSpinnerTimer();
      const flushEntries: OutputBufferEntry[] = [];
      this.flushSpinnerBuffer(flushEntries, true);
      if (flushEntries.length > 0) {
        this.emit("data", flushEntries);
      }
    }
  }

  /**
   * 获取动画压缩状态
   */
  getCompactAnimations(): boolean {
    return this.compactAnimations;
  }
}
